//
// Created by HP on 2022/1/22.
//

#include "lve_renderer.h"

#include <stdexcept>

namespace lve {

    LveRenderer::LveRenderer(LveWindow& window, LveDevice& device): lveWindow(window), lveDevice(device) {
        recreateSwapChain();
        createCommandBuffers();
    }

    LveRenderer::~LveRenderer() {
        freeCommandBuffers();
    }

    void LveRenderer::recreateSwapChain() {
        auto extent = lveWindow.getExtent();
        while (extent.width == 0 || extent.height == 0) {
            // 在窗口最小化时, 等待
            extent = lveWindow.getExtent();
            glfwWaitEvents();
        }

        // 等待当前 swapChain 使用完毕
        vkDeviceWaitIdle(lveDevice.device());
        // 创建新的 swapChain
        if (lveSwapChain == nullptr) {
            lveSwapChain = std::make_unique<LveSwapChain>(lveDevice, extent);
        } else {
            std::shared_ptr<LveSwapChain> oldSwapChain = std::move(lveSwapChain);
            lveSwapChain = std::make_unique<LveSwapChain>(lveDevice, extent, oldSwapChain);

            if (!oldSwapChain->compareSwapChainFormats(*lveSwapChain)) {
                throw std::runtime_error{"Swap chain image(or depth) format has changed!"};
            }
        }
    }

    void LveRenderer::createCommandBuffers() {
        // 在 vulkan 里, 无法直接通过函数调用执行命令
        // 而是要:
        // 1. 将命令记录在 commandBuffer 中
        // 2. 将 commandBuffer 提交到 deviceGraphicsQueue 中等待执行
        // 这样的方式的优势: 多条命令一次提交、命令复用
        commandBuffers.resize(LveSwapChain::MAX_FRAMES_IN_FLIGHT);
        VkCommandBufferAllocateInfo allocateInfo{};
        allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
        // commandBuffer 有两个 level:
        // 1. VK_COMMAND_BUFFER_LEVEL_PRIMARY
        //    可以被提交到 deviceGraphicsQueue 中执行
        //    不能被其它 commandBuffer 调用
        // 2. VK_COMMAND_BUFFER_LEVEL_SECONDARY
        //    不能被提交到 deviceGraphicsQueue 中执行
        //    可以被其它 commandBuffer 调用
        //    Secondary 可以被翻译为: 辅助
        allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
        // commandBuffer 的内存从 commandPool 中申请
        // 所以 commandPool 用于创建和销毁 commandBuffer
        // commandPool 可以看做一种 分配器 或者 内存池
        allocateInfo.commandPool = lveDevice.getCommandPool();
        // 指定 commandPool 中 commandBuffer 的数量
        allocateInfo.commandBufferCount = static_cast<uint32_t>(commandBuffers.size());

        if (vkAllocateCommandBuffers(
                lveDevice.device(), &allocateInfo, commandBuffers.data()) != VK_SUCCESS) {
            throw std::runtime_error{"failed to allocate command buffers!"};
        }

    }

    void LveRenderer::freeCommandBuffers() {
        vkFreeCommandBuffers(
                lveDevice.device(),
                lveDevice.getCommandPool(),
                commandBuffers.size(),
                commandBuffers.data()
                );
        commandBuffers.clear();
    }

    VkCommandBuffer LveRenderer::beginFrame() {
        assert(!isFrameStarted && "Cannot call beginFrame while already in progress.");
        // 用于获取将要渲染的帧的索引
        // 也会自动在双缓冲或三缓冲过程中进行 CPU 和 GPU 的同步
        auto result = lveSwapChain->acquireNextImage(&currentImageIndex);
        // 返回值 result 由 CPU 和 GPU 是否同步成功确定
        if (result == VK_ERROR_OUT_OF_DATE_KHR) {
            // 调整窗口大小时会发生
            recreateSwapChain();
            return nullptr;
        }
        if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
            throw std::runtime_error{"failed to acquire next swap chain image!"};
        }

        isFrameStarted = true;

        auto commandBuffer = getCurrentCommandBuffer();
        VkCommandBufferBeginInfo beginInfo{};
        beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
            throw std::runtime_error{"failed to begin recording command buffer!"};
        }
        return commandBuffer;
    }

    void LveRenderer::endFrame() {
        assert(isFrameStarted && "Can not call endFrame while frame is not in progress.");
        auto commandBuffer = getCurrentCommandBuffer();
        // 完成 command 记录
        if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
            throw std::runtime_error{"failed to record command buffer!"};
        }

        // 函数用于提交命令到 Device Graphics Queue, 同时处理 CPU 和 GPU 的同步
        // 在提交后 commandBuffer 会被执行
        // 然后 swapChain 会根据当前选择的限时模式在适当的时机向显示器提供相关的 ColorImageView (彩色图像?)
        auto result = lveSwapChain->submitCommandBuffers(&commandBuffer, &currentImageIndex);
        if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || lveWindow.wasWindowResized()) {
            lveWindow.resetWindowResizedFlag();
            recreateSwapChain();
        } else if (result != VK_SUCCESS) {
            throw std::runtime_error{"failed to present swap chain image!"};
        }

        isFrameStarted = false;
        currentFrameIndex = (currentFrameIndex + 1) % LveSwapChain::MAX_FRAMES_IN_FLIGHT;
    }

    void LveRenderer::beginSwapChainRenderPass(VkCommandBuffer commandBuffer) {
        assert(isFrameStarted && "Cannot call beginSwapChainRenderPass if frame is not in progress");
        assert(
            commandBuffer == getCurrentCommandBuffer() &&
            "Cannot begin render pass on command buffer from a different frame."
        );

        // 首先记录: begin a render pass (一个渲染过程 ?)
        VkRenderPassBeginInfo renderPassInfo{};
        renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
        renderPassInfo.renderPass = lveSwapChain->getRenderPass();
        // 设置这个 render pass 会被写入哪一个 frameBuffer 中
        renderPassInfo.framebuffer = lveSwapChain->getFrameBuffer(currentImageIndex);

        // 定义了 shader 加载和储存的区域
        renderPassInfo.renderArea.offset = {0, 0};
        renderPassInfo.renderArea.extent = lveSwapChain->getSwapChainExtent();

        // VKClearValue: 指定 frameBufferAttachment 的初始值
        std::array<VkClearValue, 2> clearValues{};
        // LveSwapChain::createRenderPass 中 renderPass 定义了 attachment 的格式: 先颜色, 后深度
        clearValues[0].color = {0.1f, 0.1f, 0.1f, 1.0f};
        clearValues[1].depthStencil = {1.0f, 0};
        renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
        renderPassInfo.pClearValues = clearValues.data();

        // VK_SUBPASS_CONTENTS_INLINE: 指定使用 Primary Command Buffer 中的命令, 不会使用 Secondary Command Buffer
        // VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS: 指定 使用 Secondary Command Buffer, 不用 Primary
        vkCmdBeginRenderPass(
                commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

        // 创建新的 viewPort, 使得 viewPort 可以动态变化
        VkViewport viewport{
            .x = 0.0f,
            .y = 0.0f,
            .width = static_cast<float>(lveSwapChain->getSwapChainExtent().width),
            .height = static_cast<float>(lveSwapChain->getSwapChainExtent().height),
            .minDepth = 0.0f,
            .maxDepth = 1.0f,
            };
        vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
        // 创建新的 scissor
        VkRect2D scissor {
            .offset = {.x = 0, .y = 0},
            .extent = lveSwapChain->getSwapChainExtent(),
            };
        vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
    }

    void LveRenderer::endSwapChainRenderPass(VkCommandBuffer commandBuffer) {
        assert(isFrameStarted && "Cannot call endSwapChainRenderPass if frame is not in progress");
        assert(
                commandBuffer == getCurrentCommandBuffer() &&
                "Cannot end render pass on command buffer from a different frame."
                );
        // 完成 renderPass 记录
        vkCmdEndRenderPass(commandBuffer);
    }
}